#include "uint16.h"
#include <alloc.h>
#include "str.h"
#include <string.h>
#include <stdlib.h>
#include "open.h"
#include "tai.h"
#include "cdb.h"
#include "byte.h"
#include "case.h"
#include "dns.h"
#include "seek.h"
#include "response.h"
#include "ipdb.h"
#include "env.h"
#include "scan.h"
#include "prot.h"
#include "strerr.h"
#include "qlog.h"
#define FATAL "tdlookup: fatal: "

static int want(const char *owner,const char type[2])
{
  unsigned int pos;
  static char *d;
  char x[10];
  uint16 datalen;

  pos = dns_packet_skipname(response,response_len,12); if (!pos) return 0;
  pos += 4;

  while (pos < response_len) {
    pos = dns_packet_getname(response,response_len,pos,&d); if (!pos) return 0;
    pos = dns_packet_copy(response,response_len,pos,x,10); if (!pos) return 0;
    if (dns_domain_equal(d,owner))
      if (byte_equal(type,2,x))
        return 0;
    uint16_unpack_big(x + 8,&datalen);
    pos += datalen;
  }
  return 1;
}


static char *d1;

static char clientloc[2];
static struct tai now;
static struct cdb c;
static char data[32767];
static uint32 dlen;
static unsigned int dpos;
static char type[2];
static uint32 ttl;
static int find(char *d,int flagwild)
{
  int r;
  char ch;
  struct tai cutoff;
  char ttd[8];
  char ttlstr[4];
  char recordloc[2];
  double newttl;

  for (;;) {
    r = cdb_findnext(&c,d,dns_domain_length(d));
    if (r <= 0) return r;
    dlen = cdb_datalen(&c);
    if (dlen > sizeof data) return -1;
    if (cdb_read(&c,data,dlen,cdb_datapos(&c)) == -1) return -1;
    dpos = dns_packet_copy(data,dlen,0,type,2); if (!dpos) return -1;
    dpos = dns_packet_copy(data,dlen,dpos,&ch,1); if (!dpos) return -1;
    if ((ch == '=' + 1) || (ch == '*' + 1)) {
      --ch;
      dpos = dns_packet_copy(data,dlen,dpos,recordloc,2); if (!dpos) return -1;
      if (byte_diff(recordloc,2,clientloc)) continue;
    }
    if (flagwild != (ch == '*')) continue;
    dpos = dns_packet_copy(data,dlen,dpos,ttlstr,4); if (!dpos) return -1;
    uint32_unpack_big(ttlstr,&ttl);
    dpos = dns_packet_copy(data,dlen,dpos,ttd,8); if (!dpos) return -1;
    if (byte_diff(ttd,8,"\0\0\0\0\0\0\0\0")) {
      tai_unpack(ttd,&cutoff);
      if (ttl == 0) {
	if (tai_less(&cutoff,&now)) continue;
	tai_sub(&cutoff,&cutoff,&now);
	newttl = tai_approx(&cutoff);
	if (newttl <= 2.0) newttl = 2.0;
	if (newttl >= 3600.0) newttl = 3600.0;
	ttl = newttl;
      }
      else
	if (!tai_less(&cutoff,&now)) continue;
    }
    return 1;
  }
}

static int dobytes(unsigned int len)
{
  char buf[20];
  if (len > 20) return 0;
  dpos = dns_packet_copy(data,dlen,dpos,buf,len);
  if (!dpos) return 0;
  return response_addbytes(buf,len);
}

static int doname(void)
{
  dpos = dns_packet_getname(data,dlen,dpos,&d1);
  if (!dpos) return 0;
  return response_addname(d1);
}

static int doit(char *q,char qtype[2])
{
  unsigned int bpos;
  unsigned int anpos;
  unsigned int aupos;
  unsigned int arpos;
  char *control;
  char *wild;
  int flaggavesoa;
  int flagfound;
  int r;
  int flagns;
  int flagauthoritative;
  char x[20];
  uint16 u16;
  char addr[8][4];
  int addrnum;
  uint32 addrttl;
  int i;

  anpos = response_len;

  control = q;
  for (;;) {
    flagns = 0;
    flagauthoritative = 0;
    cdb_findstart(&c);
    while (r = find(control,0)) {
      if (r == -1) return 0;
      if (byte_equal(type,2,DNS_T_SOA)) flagauthoritative = 1;
      if (byte_equal(type,2,DNS_T_NS)) flagns = 1;
    }
    if (flagns) break;
    if (!*control) return 0; /* q is not within our bailiwick */
    control += *control;
    control += 1;
  }

  if (!flagauthoritative) {
    response[2] &= ~4;
    goto AUTHORITY; /* q is in a child zone */
  }


  flaggavesoa = 0;
  flagfound = 0;
  wild = q;

  for (;;) {
    addrnum = 0;
    addrttl = 0;
    cdb_findstart(&c);
    while (r = find(wild,wild != q)) {
      if (r == -1) return 0;
      flagfound = 1;
      if (flaggavesoa && byte_equal(type,2,DNS_T_SOA)) continue;
      if (byte_diff(type,2,qtype) && byte_diff(qtype,2,DNS_T_ANY) && byte_diff(type,2,DNS_T_CNAME)) continue;
      if (byte_equal(type,2,DNS_T_A) && (dlen - dpos == 4)) {
	addrttl = ttl;
	i = dns_random(addrnum + 1);
	if (i < 8) {
	  if ((i < addrnum) && (addrnum < 8))
	    byte_copy(addr[addrnum],4,addr[i]);
	  byte_copy(addr[i],4,data + dpos);
	}
	if (addrnum < 1000000) ++addrnum;
	continue;
      }
      if (!response_rstart(q,type,ttl)) return 0;
      if (byte_equal(type,2,DNS_T_NS) || byte_equal(type,2,DNS_T_CNAME) || byte_equal(type,2,DNS_T_PTR)) {
	if (!doname()) return 0;
      }
      else if (byte_equal(type,2,DNS_T_MX)) {
	if (!dobytes(2)) return 0;
	if (!doname()) return 0;
      }
      else if (byte_equal(type,2,DNS_T_SOA)) {
	if (!doname()) return 0;
	if (!doname()) return 0;
	if (!dobytes(20)) return 0;
        flaggavesoa = 1;
      }
      else
        if (!response_addbytes(data + dpos,dlen - dpos)) return 0;
      response_rfinish(RESPONSE_ANSWER);
    }
    for (i = 0;i < addrnum;++i)
      if (i < 8) {
	if (!response_rstart(q,DNS_T_A,addrttl)) return 0;
	if (!response_addbytes(addr[i],4)) return 0;
	response_rfinish(RESPONSE_ANSWER);
      }

    if (flagfound) break;
    if (wild == control) break;
    if (!*wild) break; /* impossible */
    wild += *wild;
    wild += 1;
  }

  if (!flagfound)
    response_nxdomain();


  AUTHORITY:
  aupos = response_len;

  if (flagauthoritative && (aupos == anpos)) {
    cdb_findstart(&c);
    while (r = find(control,0)) {
      if (r == -1) return 0;
      if (byte_equal(type,2,DNS_T_SOA)) {
        if (!response_rstart(control,DNS_T_SOA,ttl)) return 0;
	if (!doname()) return 0;
	if (!doname()) return 0;
	if (!dobytes(20)) return 0;
        response_rfinish(RESPONSE_AUTHORITY);
        break;
      }
    }
  }
  else
    if (want(control,DNS_T_NS)) {
      cdb_findstart(&c);
      while (r = find(control,0)) {
        if (r == -1) return 0;
        if (byte_equal(type,2,DNS_T_NS)) {
          if (!response_rstart(control,DNS_T_NS,ttl)) return 0;
	  if (!doname()) return 0;
          response_rfinish(RESPONSE_AUTHORITY);
        }
      }
    }

  arpos = response_len;

  bpos = anpos;
  while (bpos < arpos) {
    bpos = dns_packet_skipname(response,arpos,bpos); if (!bpos) return 0;
    bpos = dns_packet_copy(response,arpos,bpos,x,10); if (!bpos) return 0;
    if (byte_equal(x,2,DNS_T_NS) || byte_equal(x,2,DNS_T_MX)) {
      if (byte_equal(x,2,DNS_T_NS)) {
        if (!dns_packet_getname(response,arpos,bpos,&d1)) return 0;
      }
      else
        if (!dns_packet_getname(response,arpos,bpos + 2,&d1)) return 0;
      case_lowerb(d1,dns_domain_length(d1));
      if (want(d1,DNS_T_A)) {
	cdb_findstart(&c);
	while (r = find(d1,0)) {
          if (r == -1) return 0;
	  if (byte_equal(type,2,DNS_T_A)) {
            if (!response_rstart(d1,DNS_T_A,ttl)) return 0;
	    if (!dobytes(4)) return 0;
            response_rfinish(RESPONSE_ADDITIONAL);
	  }
        }
      }
    }
    uint16_unpack_big(x + 8,&u16);
    bpos += u16;
  }

  if (flagauthoritative && (response_len > 512)) {
    byte_zero(response + RESPONSE_ADDITIONAL,2);
    response_len = arpos;
    if (response_len > 512) {
      byte_zero(response + RESPONSE_AUTHORITY,2);
      response_len = aupos;
    }
  }

  return 1;
}

int respond(char *q,char qtype[2],char ip[4])
{
  int fd;
  int r = 0;
	//replace char t[] with stralloc t ?
	char t[256];
	char u[64];
	char *z = NULL;
	int l_idx[32]; //labels indexed in fqdn,including first pos (0)
	register int i;
	int l = 256;
	stralloc fqdn_map;
	int len;
	unsigned short multimode = 0;
	unsigned short ipmap = 0;
	unsigned short multicdb = 0;
	unsigned short multiuser = 0;
  tai_now(&now);
	fd = open_read("data.cdb");
	if (fd == -1) return 0;
	cdb_init(&c,fd);
	byte_zero(clientloc,2);
	fqdn_map.s = NULL;
	fqdn_map.len = 0;
	// OPMODE: MULTICDB == 2; MULTIUSER == 1
	z = env_get("IPMAP"); if (z) scan_ushort((const char *)z,&ipmap);
	z = env_get("MULTICDB"); if (z) scan_ushort((const char *)z, &multicdb);
	z = env_get("MULTIUSER"); if (z) scan_ushort((const char *)z, &multiuser);
	multimode = multicdb | multiuser;
	if (multimode) {
#ifdef TESTMODE
		fprintf(stderr,"multimode: lookup fqdn map in progress\n");
#endif
		fqdn_read(t,l_idx,&l,q);
		for (i = 0; i < 32 && l_idx[i] != -1; i++) {
#ifdef TESTMODE
			fprintf(stderr,"check: %s\n", t+l_idx[i]);
#endif
			stralloc_copyb(&fqdn_map,"\0?",2);
			stralloc_catb(&fqdn_map,t+l_idx[i],l-l_idx[i]);
			r = cdb_find(&c,fqdn_map.s,fqdn_map.len);
			if (r) break;
			if (r == -1) return 0;
		}
		if (!r) return 0;
	}

	if (!r) {
#ifdef TESTMODE
		fprintf(stderr,"singlemode: lookup_ipdbmap? %s\n", (ipmap)?"yes":"no");
#endif
		if ( ipmap &&  !ipdb_get(&c,ip,NULL,0,clientloc)) return 0;
	} else {
		len = cdb_datalen(&c);
		if (len > 63) return 0;
		if (cdb_read(&c,u,len,cdb_datapos(&c)) == -1) return 0;
		u[len] = '\0';
#ifdef TESTMODE
		fprintf(stderr,"multimode: use_map %s <-> %s, use_cdb_mapping: %s\n", t+l_idx[i], u+1, 
			(u[0] == '1' && ipmap)?"yes":"no");
#endif
		if (u[0] == '1' && ipmap ) {
			clientloc[0] = 'X';
			clientloc[1] = 'X';
		} 
		if (multicdb) {
#ifdef TESTMODE
			fprintf(stderr,"MULTICDB mode: reopen(data.cdb,%s)\n", u+1);
#endif
			cdb_free(&c);
			close(fd);
			fd = open_read(u+1);
			if (fd == -1) return 0;
			cdb_init(&c,fd);
			if (u[0] == '1' && ipmap ) {
#ifdef TESTMODE
				fprintf(stderr,"using multicdb multimode with ipdbmap\n");
#endif
				if (!ipdb_get(&c,ip,NULL,0,clientloc)) return 0;
			}
		} else {
#ifdef TESTMODE
			fprintf(stderr,"get data from same cdb for ukey %s (%c) %d\n", u+1, u[0],len);
#endif
			if (u[0] == '1' && ipmap && multiuser) {
				if (!ipdb_get(&c,ip,u+1,len-1,clientloc)) return 0;
#ifdef TESTMODE
				fprintf(stderr,"got clientloc as %c%c\n", clientloc[0],clientloc[1]);
#endif
			}
		}
	}
  r = doit(q,qtype);
  cdb_free(&c);
  close(fd);
  return r;
}

